package io.gitee.zhangbinhub.acp.boot.websocket

import io.gitee.zhangbinhub.acp.boot.component.BootLogAdapter
import io.gitee.zhangbinhub.acp.boot.exceptions.WebSocketException
import io.gitee.zhangbinhub.acp.boot.log.LogAdapter
import org.java_websocket.handshake.ServerHandshake
import org.noear.java_websocket.client.SimpleWebSocketClient
import org.noear.solon.annotation.Inject
import java.net.URI
import java.nio.ByteBuffer
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit

abstract class AcpWebSocketClient {
    @Inject
    protected var logAdapter: LogAdapter = BootLogAdapter()

    @JvmOverloads
    @Throws(WebSocketException::class)
    fun open(
        clientId: String,
        uri: URI,
        headers: Map<String, String>? = null,
        timeOut: Long = 10000,
        timeUnit: TimeUnit = TimeUnit.MILLISECONDS
    ): WebSocketClientHandle = try {
        close(clientId)
        object : WebSocketClientHandle(logAdapter, uri, clientId) {
            override fun onOpen(handShakeData: ServerHandshake?) {
                logAdapter.info("WebSocket client [clientId=$clientId] open")
                acpWebSocketInstanceMap[clientId] = this
                sessionOpen(this)
            }

            override fun onClose(code: Int, reason: String?, remote: Boolean) {
                super.onClose(code, reason, remote)
                acpWebSocketInstanceMap.remove(clientId)
                sessionClose(this, code, reason, remote)
            }

            override fun onMessage(message: String?) {
                receiveMessage(this, message)
            }

            override fun onMessage(message: ByteBuffer?) {
                receiveMessage(this, message)
            }

            override fun onError(ex: Exception?) {
                sessionError(this, ex)
            }
        }.also { client ->
            headers?.forEach { (key, value) ->
                client.addHeader(key, value)
            }
            client.connectBlocking(timeOut, timeUnit)
        }
    } catch (e: Exception) {
        logAdapter.error(e.message, e)
        close(clientId)
        throw WebSocketException("WebSocket client connect failed!")
    }

    fun close(clientId: String) {
        getInstance(clientId)?.release()
    }

    abstract fun sessionOpen(handle: WebSocketClientHandle)
    abstract fun sessionClose(handle: WebSocketClientHandle?, code: Int, reason: String?, remote: Boolean)
    abstract fun sessionError(handle: WebSocketClientHandle?, throwable: Throwable?)
    abstract fun receiveMessage(handle: WebSocketClientHandle?, message: String?)
    abstract fun receiveMessage(handle: WebSocketClientHandle?, message: ByteBuffer?)

    companion object {
        private val log: LogAdapter = BootLogAdapter()
        private val acpWebSocketInstanceMap: ConcurrentHashMap<String, WebSocketClientHandle> =
            ConcurrentHashMap<String, WebSocketClientHandle>()

        @JvmStatic
        fun getInstance(clientId: String): WebSocketClientHandle? = acpWebSocketInstanceMap[clientId]

        @JvmStatic
        fun getInstanceList(): List<WebSocketClientHandle> = acpWebSocketInstanceMap.map { entry -> entry.value }

        /**
         * 主动发送消息到客户端
         * @param message 消息
         * @param clientId 客户端唯一标识
         */
        @JvmStatic
        fun sendMessageToServer(message: String, clientId: String): Boolean =
            getInstance(clientId).let { instance ->
                if (instance != null) {
                    instance.sendMessage(message)
                } else {
                    log.error("WebSocket client[$clientId] is not found!")
                    false
                }
            }

        /**
         * 主动发送消息到客户端
         * @param message 消息
         * @param clientId 客户端唯一标识
         */
        @JvmStatic
        fun sendMessageToServer(message: ByteBuffer, clientId: String): Boolean =
            getInstance(clientId).let { instance ->
                if (instance != null) {
                    instance.sendMessage(message)
                } else {
                    log.error("WebSocket client[$clientId] is not found!")
                    false
                }
            }

        /**
         * 主动发送消息到客户端
         * @param message 消息
         */
        @JvmStatic
        fun sendMessageToServer(message: String) {
            getInstanceList().forEach { instance ->
                if (!instance.sendMessage(message)) {
                    log.error("WebSocket client[${instance.clientId}] send message failed!")
                }
            }
        }

        /**
         * 主动发送消息到客户端
         * @param message 消息
         */
        @JvmStatic
        fun sendMessageToServer(message: ByteBuffer) {
            getInstanceList().forEach { instance ->
                if (!instance.sendMessage(message)) {
                    log.error("WebSocket client[${instance.clientId}] send message failed!")
                }
            }
        }
    }

    abstract class WebSocketClientHandle(
        private val logAdapter: LogAdapter?,
        uri: URI,
        val clientId: String
    ) : SimpleWebSocketClient(uri) {
        fun sendMessage(message: String): Boolean = if (beforeSendMessage()) {
            try {
                this.send(message)
                true
            } catch (e: Exception) {
                logAdapter?.error(e.message, e)
                false
            }
        } else {
            false
        }

        fun sendMessage(message: ByteBuffer): Boolean = if (beforeSendMessage()) {
            try {
                this.send(message)
                true
            } catch (e: Exception) {
                logAdapter?.error(e.message, e)
                false
            }
        } else {
            false
        }

        private fun beforeSendMessage(): Boolean = if (this.isOpen) {
            true
        } else {
            logAdapter?.error("WebSocket session is null or closed!")
            false
        }
    }
}